package com.smedi.ismedi.constructionproject.core.port.adapter.persistence.repository.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.smedi.ismedi.authority.starter.util.AuthorityUtil;
import com.smedi.ismedi.authority.starter.util.VerifyHelper;
import com.smedi.ismedi.constructionproject.core.application.query.vo.HrOrganizationVo;
import com.smedi.ismedi.constructionproject.core.application.query.vo.ProjectDetailVo;
import com.smedi.ismedi.constructionproject.core.domain.aggregatemodel.*;
import com.smedi.ismedi.constructionproject.core.domain.aggregatemodel.dto.ProjectAssignmentQuery;
import com.smedi.ismedi.constructionproject.core.domain.aggregatemodel.dto.ProjectCountQuery;
import com.smedi.ismedi.constructionproject.core.domain.aggregatemodel.dto.ProjectQuery;
import com.smedi.ismedi.constructionproject.core.domain.repository.ProjectAssignmentRepository;
import com.smedi.ismedi.constructionproject.core.domain.repository.ProjectBasicRepository;
import com.smedi.ismedi.constructionproject.core.port.adapter.persistence.model.ProjectBasicEntity;
import com.smedi.ismedi.constructionproject.core.port.adapter.persistence.mybatis.mapper.ProjectBasicMapper;
import com.smedi.ismedi.constructionproject.core.port.adapter.restful.convert.AdapterConverter;
import com.smedi.ismedi.database.starter.annotation.SkipSqlPermission;
import com.smedi.ismedi.organization.api.dto.request.OrganizationListQueryRequest;
import com.smedi.ismedi.organization.api.dto.response.resp.OrganizationResp;
import com.smedi.ismedi.organization.api.feign.RemoteHrOrganizationFeignApi;
import com.smedi.ismedi.personnel.api.dto.request.PersonListQueryRequest;
import com.smedi.ismedi.personnel.api.dto.response.resp.PersonResp;
import com.smedi.ismedi.personnel.api.feign.RemotePersonCoreFeignApi;
import com.smedi.ismedi.video.monitor.api.dto.request.MonitorSettingQueryListRequest;
import com.smedi.ismedi.video.monitor.api.dto.response.MonitorSettingResponse;
import com.smedi.ismedi.video.monitor.api.feign.RemoteMonitorSettingFeignApi;
import com.smedi.ismedi.web.starter.util.DozerUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.smedi.ismedi.constructionproject.core.infrastructure.constant.ConstantField.ACCESS_PROJECT;
import static com.smedi.ismedi.constructionproject.core.infrastructure.constant.ConstantField.PROJECT_MANAGER;
import static com.smedi.ismedi.web.starter.thread.ThreadPool.IO_INSTANCE;

/**
 * @Author: xulinjun
 * @Date: 2021-6-18 15:54
 */
@Slf4j
@Repository
@AllArgsConstructor
public class ProjectBasicRepositoryImpl extends ServiceImpl<ProjectBasicMapper, ProjectBasicEntity> implements ProjectBasicRepository, IService<ProjectBasicEntity> {
    private final DozerUtil dozerUtil;
    private final AuthorityUtil authorityUtil;
    private final AdapterConverter adapterConverter;
    private final RemoteHrOrganizationFeignApi remoteHrOrganizationFeignApi;
    private final RemoteMonitorSettingFeignApi remoteMonitorSettingFeignApi;
    private final RemotePersonCoreFeignApi remotePersonCoreFeignApi;
    private final ProjectAssignmentRepository projectAssignmentRepository;

    @Override
    public ProjectBasic selectByProjectId(Long projectId) {
        ProjectBasicEntity row = this.getBaseMapper().selectById(projectId);
        if (row != null) {
            // 需要考虑软删除这块内容
            return dozerUtil.convertor(row, ProjectBasic.class);
        } else {
            return null;
        }
    }

    @Override
    public List<ProjectBasic> selectByProjectIds(List<Long> projectIds) {
        if (CollectionUtils.isEmpty(projectIds)) {
            return new ArrayList<>();
        }
        List<ProjectBasicEntity> projectBasicEntities = this.getBaseMapper().selectBatchIds(projectIds);
        return dozerUtil.convertor(projectBasicEntities, ProjectBasic.class);
    }

    @Override
    public List<ProjectBasic> selectByProvinceName(String provinceName) {
        QueryWrapper<ProjectBasicEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("id");

        queryWrapper.eq("project_address_province", provinceName);

        List<ProjectBasicEntity> projectBasicEntities = this.getBaseMapper().selectList(queryWrapper);
        return dozerUtil.convertor(projectBasicEntities, ProjectBasic.class);
    }

    @Override
    public int selectProjectCodeCount(String projectCode, Long projectId) {
        QueryWrapper<ProjectBasicEntity> wrapper = new QueryWrapper();
        wrapper.eq("project_code", projectCode);
        if (projectId != null) {
            wrapper.ne("id", projectId);
        }
        return this.getBaseMapper().selectCount(wrapper);
    }

    @Override
    public ProjectBasic doInsert(ProjectBasic data) {
        ProjectBasicEntity projectBasicEntity = dozerUtil.convertor(data, ProjectBasicEntity.class);
        this.getBaseMapper().insert(projectBasicEntity);
        return dozerUtil.convertor(projectBasicEntity, ProjectBasic.class);
    }

    @Override
    public ProjectBasic doUpdate(ProjectBasic data) {
        ProjectBasicEntity projectBasicEntity = dozerUtil.convertor(data, ProjectBasicEntity.class);
        this.getBaseMapper().updateById(projectBasicEntity);
        return dozerUtil.convertor(projectBasicEntity, ProjectBasic.class);
    }

    @Override
    public int selectProjectCountByHrOrganizationId(Long hrOrganizationId) {
        QueryWrapper<ProjectBasicEntity> wrapper = new QueryWrapper();
        wrapper.eq("hr_organization_id", hrOrganizationId);
        return this.getBaseMapper().selectCount(wrapper);
    }

    @Override
    @SkipSqlPermission
    public List<ProjectBasic> selectProjectList() {
        QueryWrapper<ProjectBasicEntity> wrapper = new QueryWrapper<>();

        wrapper.orderByDesc("id");

        return dozerUtil.convertor(this.getBaseMapper().selectList(wrapper), ProjectBasic.class);
    }

    @Override
    @SkipSqlPermission
    public List<ProjectBasic> selectProjectListWithToken() {
        // 权限所属的projectId
        Map<String, List<Long>> dataScope = authorityUtil.getDataScope();
        List<Long> projectIdScope = dataScope.getOrDefault("project", null);
        if (null == projectIdScope) {
            return null;
        }
        HashSet<Long> projectIdSet = new HashSet<>(projectIdScope);
        projectIdScope.clear();
        projectIdScope.addAll(projectIdSet);

        QueryWrapper<ProjectBasicEntity> wrapper = new QueryWrapper<>();
        if (!projectIdScope.isEmpty()) {
            wrapper.in("id", projectIdScope);
        }
        wrapper.orderByDesc("id");

        return dozerUtil.convertor(this.getBaseMapper().selectList(wrapper), ProjectBasic.class);
    }

    @Override
    public void doDeleteByProjectId(Long projectId) {
        QueryWrapper<ProjectBasicEntity> wrapper = new QueryWrapper();
        wrapper.eq("id", projectId);

        this.getBaseMapper().delete(wrapper);
    }

    @Override
    public List<ProjectBasic> selectByHrOrganizationId(Long hrOrganizationId) {
        QueryWrapper<ProjectBasicEntity> wrapper = new QueryWrapper();
        wrapper.eq("hr_organization_id", hrOrganizationId);
        return dozerUtil.convertor(this.getBaseMapper().selectList(wrapper), ProjectBasic.class);
    }

    @Override
    public List<ProjectBasic> getQueryProjectBasicListByApp(String appSeach,List<Long> ids) {
        QueryWrapper<ProjectBasicEntity> wrapper = new QueryWrapper();
        if(ids.size() > 0){
            wrapper.in("id",ids);
        }
        wrapper.like("project_name", appSeach).or().like("project_manager",appSeach);
        return dozerUtil.convertor(this.getBaseMapper().selectList(wrapper), ProjectBasic.class);
    }

    @Override
    public Project getProjectOne(ProjectQuery projectQuery) {

        LambdaQueryWrapper<ProjectBasicEntity> lambdaQueryWrapper = this.projectQueryToLambdaQueryWrapper(projectQuery);

        if (VerifyHelper.isNotAvailable(lambdaQueryWrapper)) {
            return null;
        }

        ProjectBasicEntity projectBasicEntity = this.getBaseMapper().selectOne(lambdaQueryWrapper);
        return dozerUtil.convertor(projectBasicEntity, Project.class);
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<Project> getProjectList(ProjectQuery projectQuery) {

        LambdaQueryWrapper<ProjectBasicEntity> lambdaQueryWrapper = this.projectQueryToLambdaQueryWrapper(projectQuery);

        if (VerifyHelper.isNotAvailable(lambdaQueryWrapper)) {
            return new ArrayList<>();
        }

        lambdaQueryWrapper.orderBy(true, false, ProjectBasicEntity::getId);

        List<ProjectBasicEntity> projectBasicEntities = this.getBaseMapper().selectList(lambdaQueryWrapper);
        return dozerUtil.convertor(projectBasicEntities, Project.class);
    }

    @Override
    public Page<ProjectDetailVo> getProjectDetailPage(ProjectQuery projectQuery) {
        Page<ProjectBasicEntity> projectEntityPage = new Page<>(projectQuery.getCurrent(), projectQuery.getSize());

        LambdaQueryWrapper<ProjectBasicEntity> lambdaQueryWrapper = this.projectQueryToLambdaQueryWrapper(projectQuery);

        if (VerifyHelper.isNotAvailable(lambdaQueryWrapper)) {
            return new Page<>(projectQuery.getCurrent(), projectQuery.getSize());
        }

        Page<ProjectBasicEntity> projectBasicEntityPage = this.getBaseMapper().selectPage(projectEntityPage, lambdaQueryWrapper);

        Page<ProjectDetailVo> projectPage = adapterConverter.projectEntityPageToProjectDetailVoPage(projectBasicEntityPage);
        projectPage.setRecords(this.extend(dozerUtil.convertor(projectBasicEntityPage.getRecords(), Project.class)));

        return projectPage;
    }

    @Override
    public List<ProjectDetailVo> getProjectDetailList(ProjectQuery projectQuery) {
        List<Project> projects = this.getProjectList(projectQuery);
        return this.extend(projects);
    }

    @Override
    public long updateProjectByProjectName(String projectName, String dingDeptKey) {
        return this.getBaseMapper().updateDingDeptKeyByProjectName(projectName, dingDeptKey);
    }

    @Override
    public List<ProjectWithDing> selectByOrgId(Long orgId) {
        LambdaQueryWrapper<ProjectBasicEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ProjectBasicEntity::getHrOrganizationId, orgId)
                .eq(ProjectBasicEntity::isDeleted, false);
        List<ProjectBasicEntity> projectBasicEntities = this.getBaseMapper().selectList(lambdaQueryWrapper);
        return dozerUtil.convertor(projectBasicEntities, ProjectWithDing.class);
    }

    @Override
    public ProjectWithDing selectById(Long id) {
        ProjectBasicEntity entity = this.baseMapper.selectById(id);
        return dozerUtil.convertor(entity, ProjectWithDing.class);
    }

    @Override
    public Integer getProjectCount(ProjectCountQuery projectCountQuery) {
        LambdaQueryWrapper<ProjectBasicEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.in(CollectionUtils.isNotEmpty(projectCountQuery.getIds()), ProjectBasicEntity::getId, projectCountQuery.getIds());
        lambdaQueryWrapper.in(CollectionUtils.isNotEmpty(projectCountQuery.getMonitorTypes()), ProjectBasicEntity::getMonitorType, projectCountQuery.getMonitorTypes());
        return this.baseMapper.selectCount(lambdaQueryWrapper);
    }

    private LambdaQueryWrapper<ProjectBasicEntity> projectQueryToLambdaQueryWrapper(ProjectQuery projectQuery) {
        LambdaQueryWrapper<ProjectBasicEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();

        boolean hasAboutIdQuery = false;
        boolean hasAboutOrganizationIdQuery = false;
        Set<Long> projectIds = new HashSet<>();
        Set<Long> organizationIds = new HashSet<>();
        if (VerifyHelper.isAvailable(projectQuery.getId())) {
            projectIds.add(projectQuery.getId());
            hasAboutIdQuery = true;
        }
        if (VerifyHelper.isAvailable(projectQuery.getIds())) {
            projectIds.addAll(projectQuery.getIds());
            hasAboutIdQuery = true;
        }
        if (VerifyHelper.isAvailable(projectQuery.getHrOrganizationId())) {
            organizationIds.add(projectQuery.getHrOrganizationId());
            hasAboutOrganizationIdQuery = true;
        }
        if (VerifyHelper.isAvailable(projectQuery.getHrOrganizationIds())) {
            organizationIds.addAll(projectQuery.getHrOrganizationIds());
            hasAboutOrganizationIdQuery = true;
        }
        if (VerifyHelper.isAvailable(projectQuery.getByAuthenticationFilter()) && projectQuery.getByAuthenticationFilter()) {
            List<Long> projectIdsWithAccess = authorityUtil.getDataScope().getOrDefault(ACCESS_PROJECT, new ArrayList<>());
            if (VerifyHelper.isNotAvailable(projectIds)) {
                projectIds.addAll(projectIdsWithAccess);
            }
            if (VerifyHelper.isAvailable(projectIds)) {
                projectIds.retainAll(projectIdsWithAccess);
            }
        }

        if (VerifyHelper.isAvailable(projectQuery.getOrganizationShortName())) {
            List<OrganizationResp> organizations = remoteHrOrganizationFeignApi.getOrganizationList(OrganizationListQueryRequest.builder()
                    .shortName(projectQuery.getOrganizationShortName())
                    .build()).getData();
            if (VerifyHelper.isNotAvailable(organizations)) {
                return null;
            }
            if (VerifyHelper.isNotAvailable(organizationIds)) {
                organizationIds.addAll(organizations.stream().map(OrganizationResp::getId).collect(Collectors.toList()));
            }
            if (VerifyHelper.isAvailable(organizationIds)) {
                organizationIds.retainAll(organizations.stream().map(OrganizationResp::getId).collect(Collectors.toList()));
            }
            hasAboutOrganizationIdQuery = true;
        }

        if (VerifyHelper.isAvailable(projectQuery.getHasVideoMonitor())) {
            List<MonitorSettingResponse> monitorSettingResponses = remoteMonitorSettingFeignApi.getMonitorSettingSimpleList(MonitorSettingQueryListRequest.builder()
                    .build()).getData();
            if (projectQuery.getHasVideoMonitor() && VerifyHelper.isAvailable(monitorSettingResponses)) {
                if (VerifyHelper.isNotAvailable(projectIds)) {
                    projectIds.addAll(monitorSettingResponses.stream().map(MonitorSettingResponse::getProjectId).collect(Collectors.toList()));
                }
                if (VerifyHelper.isAvailable(projectIds)) {
                    projectIds.retainAll(monitorSettingResponses.stream().map(MonitorSettingResponse::getProjectId).collect(Collectors.toList()));
                }
            }
            if (!projectQuery.getHasVideoMonitor() && VerifyHelper.isAvailable(monitorSettingResponses)) {
                if (VerifyHelper.isAvailable(projectIds)) {
                    monitorSettingResponses.stream().map(MonitorSettingResponse::getProjectId).collect(Collectors.toList()).forEach(projectIds::remove);
                }
            }
            hasAboutIdQuery = true;
        }

        if (hasAboutIdQuery && VerifyHelper.isNotAvailable(projectIds)) {
            return null;
        }

        if (hasAboutOrganizationIdQuery && VerifyHelper.isNotAvailable(organizationIds)) {
            return null;
        }

        if (VerifyHelper.isAvailable(projectQuery.getCodeOrManagerOrName())) {
            lambdaQueryWrapper.like(ProjectBasicEntity::getProjectName, projectQuery.getCodeOrManagerOrName())
                    .or().like(ProjectBasicEntity::getProjectCode, projectQuery.getCodeOrManagerOrName())
                    .or().like(ProjectBasicEntity::getProjectManager, projectQuery.getCodeOrManagerOrName());
        }

        lambdaQueryWrapper.in(VerifyHelper.isAvailable(organizationIds), ProjectBasicEntity::getHrOrganizationId, organizationIds);
        lambdaQueryWrapper.in(VerifyHelper.isAvailable(projectIds), ProjectBasicEntity::getId, projectIds);
        lambdaQueryWrapper.eq(VerifyHelper.isAvailable(projectQuery.getProjectStatus()), ProjectBasicEntity::getProjectStatus, projectQuery.getProjectStatus());
        lambdaQueryWrapper.in(VerifyHelper.isAvailable(projectQuery.getProjectStatuses()), ProjectBasicEntity::getProjectStatus, projectQuery.getProjectStatuses());
        lambdaQueryWrapper.eq(VerifyHelper.isAvailable(projectQuery.getMonitorType()), ProjectBasicEntity::getMonitorType, projectQuery.getMonitorType());
        lambdaQueryWrapper.in(VerifyHelper.isAvailable(projectQuery.getMonitorTypes()), ProjectBasicEntity::getMonitorType, projectQuery.getMonitorTypes());
        lambdaQueryWrapper.eq(VerifyHelper.isAvailable(projectQuery.getEpcType()), ProjectBasicEntity::getEpcType, projectQuery.getEpcType());
        lambdaQueryWrapper.in(VerifyHelper.isAvailable(projectQuery.getEpcTypes()), ProjectBasicEntity::getEpcType, projectQuery.getEpcTypes());
        lambdaQueryWrapper.ge(VerifyHelper.isAvailable(projectQuery.getActualDurationBeginGe()), ProjectBasicEntity::getActualDurationBegin, projectQuery.getActualDurationBeginGe());
        lambdaQueryWrapper.le(VerifyHelper.isAvailable(projectQuery.getActualDurationEndLe()), ProjectBasicEntity::getActualDurationEnd, projectQuery.getActualDurationEndLe());

        if (projectQuery.getInShanghai() != null && projectQuery.getInShanghai()) {
            lambdaQueryWrapper.like(ProjectBasicEntity::getProjectAddress, "上海市");
        }

        return lambdaQueryWrapper;
    }

    @Override
    public Boolean updateProjectBasic(ProjectBasic projectBasic) {
        ProjectBasicEntity projectBasicEntity = adapterConverter.projectBasicToProjectBasicEntity(projectBasic);
        return baseMapper.updateById(projectBasicEntity) != 0;

    }

    private ProjectDetailVo extend(Project project) {
        if (project == null) {
            return null;
        }

        List<Project> projects = new ArrayList<>();
        projects.add(project);
        List<ProjectDetailVo> projectDetailVos = this.extend(projects);

        return projectDetailVos.get(0);
    }

    private List<ProjectDetailVo> extend(List<Project> projects) {
        if (projects.isEmpty()) {
            return new ArrayList<>();
        }

        List<ProjectDetailVo> projectDetailVos = new ArrayList<>();
        Set<Long> hrOrganizationIds = new HashSet<>();
        Set<Long> projectIds = new HashSet<>();
        for (Project project : projects) {
            projectIds.add(project.getId());
            Long hrOrganizationId = project.getHrOrganizationId();
            if (hrOrganizationId != null) {
                hrOrganizationIds.add(hrOrganizationId);
            }
        }

        Map<Long, HrOrganizationVo> idHrOrganizationVoMap = new HashMap<>(128);

        Map<Long, Long> projectIdProjectManagerIdMap = new HashMap<>();
        Map<Long, Person> idPersonMap = new HashMap<>();
        Set<Long> personIds = new HashSet<>();
        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    List<ProjectAssignment> projectAssignments = projectAssignmentRepository.getProjectAssignmentList(ProjectAssignmentQuery.builder().projectIds(new ArrayList<>(projectIds)).projectJob(PROJECT_MANAGER).build());
                    for (ProjectAssignment projectAssignment : projectAssignments) {
                        personIds.add(projectAssignment.getPersonId());
                        projectIdProjectManagerIdMap.put(projectAssignment.getProjectId(), projectAssignment.getPersonId());
                    }
                }, IO_INSTANCE).thenRunAsync(() -> {
                    List<PersonResp> personResps = remotePersonCoreFeignApi.getPersonList(PersonListQueryRequest.builder().ids(new ArrayList<>(personIds)).build()).getData();
                    List<Person> people = adapterConverter.personRespToPerson(personResps);
                    for (Person person : people) {
                        idPersonMap.put(person.getId(), person);
                    }
                }, IO_INSTANCE),
                CompletableFuture.runAsync(() -> {
                    if (VerifyHelper.isAvailable(hrOrganizationIds)) {
                        List<OrganizationResp> organizationResps = remoteHrOrganizationFeignApi.getOrganizationList(OrganizationListQueryRequest.builder().ids(new ArrayList<>(hrOrganizationIds)).build()).getData();
                        List<HrOrganizationVo> hrOrganizationVos = adapterConverter.organizationRespToHrOrganizationVo(organizationResps);
                        for (HrOrganizationVo hrOrganizationVo : hrOrganizationVos) {
                            idHrOrganizationVoMap.put(hrOrganizationVo.getId(), hrOrganizationVo);
                        }
                    }
                }, IO_INSTANCE)
        ).join();

        for (Project project : projects) {
            ProjectDetailVo projectDetailVo = adapterConverter.projectToProjectDetailVo(project);

            projectDetailVo.setHrOrganization(idHrOrganizationVoMap.getOrDefault(project.getHrOrganizationId(), HrOrganizationVo.builder().id(project.getHrOrganizationId()).build()));
            Long projectManagerId = projectIdProjectManagerIdMap.get(project.getId());
            if (VerifyHelper.isAvailable(projectManagerId)) {
                projectDetailVo.setProjectManager(idPersonMap.get(projectManagerId));
            }

            projectDetailVos.add(projectDetailVo);
        }

        return projectDetailVos;
    }
}
